<?php


/**
 * xml文件解析.
 *
 * @author Qian Su <aoxue.1988.su.qian@163.com>
 * @copyright ©2003-2103 phpwind.com
 * @license http://www.windframework.com
 *
 * @version $Id: WindXmlParser.php 3904 2013-01-08 07:01:26Z yishuo $
 */
class WindXmlParser
{
    /**
     * @var string 节点名称
     */
    const NAME = 'name';

    /**
     * @var Domdocument DOM解析器
     */
    private $dom = null;

    /**
     * 初始化xml解析器.
     *
     * @param string $version xml版本
     * @param string $encode  xml编码
     */
    public function __construct($version = '1.0', $encode = 'utf-8')
    {
        if (!class_exists('DOMDocument')) {
            throw new WindException('[parser.WindXmlParser] DOMDocument is not exist.');
        }
        $this->dom = new DOMDocument($version, $encode);
    }

    /**
     * 解析xml文件.
     *
     * @param string $filename 待解析文件名
     * @param int    $option   解析选项,默认为0
     *
     * @return array
     */
    public function parse($filename, $option = 0)
    {
        if (!is_file($filename)) {
            return array();
        }
        $this->dom->load($filename, $option);

        return $this->getChilds($this->dom->documentElement);
    }

    /**
     * 将数据内容解析成数组格式.
     *
     * @param string $stream 数据内容
     * @param int    $option 解析选项,默认为0
     *
     * @return array
     */
    public function parseXmlStream($stream, $option = 0)
    {
        if (!$stream) {
            return array();
        }
        $this->dom->loadXML($stream, $option);

        return $this->getChilds($this->dom->documentElement);
    }

    /**
     * 将数据转换成xml格式.
     *
     * <code>
     * 数组中key为数值型时，则转为<item id=key>value</item>
     * 普通string或其他基本类型，则转为<item>string</item>
     * </code>
     *
     * @param mixed  $source  待转换的数据
     * @param string $charset 待转换数据的编码
     *
     * @return string
     */
    public function parseToXml($source, $charset = 'utf8')
    {
        switch (gettype($source)) {
            case 'object':
                $source = get_object_vars($source);
            case 'array':
                $this->arrayToXml($source, $charset, $this->dom);
                break;
            case 'string':
                $source = WindConvert::convert($source, 'utf8', $charset);
            default:
                $item = $this->dom->createElement('item');
                $text = $this->dom->createTextNode($source);
                $item->appendChild($text);
                $this->dom->appendChild($item);
                break;
        }

        return $this->dom->saveXML();
    }

    /**
     * 获得节点的所有子节点.
     *
     * 子节点包括属性和子节点（及文本节点),
     * 子节点的属性将会根据作为该节点的一个属性元素存放，如果该子节点中含有标签列表，则会进行一次合并。
     * 每个被合并的列表项都作为一个单独的数组元素存在。
     *
     * @param DOMElement $node 要解析的XMLDOM节点
     *
     * @return array 返回解析后该节点的数组
     */
    public function getChilds($node)
    {
        if (!$node instanceof DOMElement) {
            return array();
        }
        $childs = array();
        foreach ($node->childNodes as $_node) {
            $tempChilds = $attributes = array();
            $_node->hasAttributes() && $attributes = $this->getAttributes($_node);
            if (3 == $_node->nodeType) {
                $value = trim($_node->nodeValue);
                (is_numeric($value) || $value) && $childs['__value'] = $value; //值为0的情况
                $__tmp = strtolower($value);
                ('false' === $__tmp) && $childs['__value'] = false; //为false的配置值
                ('true' === $__tmp) && $childs['__value'] = true; //为false的配置值
            }
            if (1 !== $_node->nodeType) {
                continue;
            }

            $tempChilds = $this->getChilds($_node);
            $tempChilds = array_merge($attributes, $tempChilds);

            if (empty($tempChilds)) {
                $tempChilds = '';
            } else {
                $tempChilds = (isset($tempChilds['__value']) && count($tempChilds) == 1) ? $tempChilds['__value'] : $tempChilds;
            }

            $nodeName = '' !== ($name = $_node->getAttribute(self::NAME)) ? $name : $_node->nodeName;
            if (!isset($childs[$nodeName])) {
                $childs[$nodeName] = $tempChilds;
            } else {
                $element = $childs[$nodeName];
                $childs[$nodeName] = (is_array($element) && !is_numeric(implode('', array_keys($element)))) ? array_merge(
                    array($element), array($tempChilds)) : array_merge((array) $element, array($tempChilds));
            }
        }

        return $childs;
    }

    /**
     * 获得节点的属性.
     *
     * 该属性将不包含属性为name的值--规则（name的值将作为解析后数组的key值索引存在）
     *
     * @param DOMElement $node 节点
     *
     * @return array 返回属性数组
     */
    public function getAttributes($node)
    {
        if (!$node instanceof DOMElement || !$node->hasAttributes()) {
            return array();
        }
        $attributes = array();
        foreach ($node->attributes as $attribute) {
            if (self::NAME != $attribute->nodeName) {
                $value = (string) $attribute->nodeValue;
                $__tmp = strtolower($value);
                $attributes[$attribute->nodeName] = 'false' === $__tmp ? false : ('true' === $__tmp ? true : $value);
            }
        }

        return $attributes;
    }

    /**
     * 将一个数组转换为xml.
     *
     * @param array       $arr     待转换的数组
     * @param string      $charset 编码
     * @param DOMDocument $dom     根节点
     *
     * @return DOMDocument
     */
    protected function arrayToXml($arr, $charset, $dom = null)
    {
        foreach ($arr as $key => $val) {
            if (is_numeric($key)) {
                $itemx = $this->dom->createElement('item');
                $id = $this->dom->createAttribute('id');
                $id->appendChild($this->dom->createTextNode($key));
                $itemx->appendChild($id);
            } else {
                $itemx = $this->dom->createElement($key);
            }
            $dom->appendChild($itemx);
            if (is_string($val)) {
                $val = WindConvert::convert($val, 'utf8', $charset);
                $itemx->appendChild($this->dom->createTextNode($val));
            } elseif (is_object($val)) {
                $this->arrayToXml(get_object_vars($val), $charset, $itemx);
            } else {
                $this->arrayToXml($val, $charset, $itemx);
            }
        }
    }
}
